/*-------------------------------------------------------------------------
 *
 * rls.c
 *		  与RLS相关的实用功能。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, 加利福尼亚大学董事会
 *
 *
 * IDENTIFICATION
 *		  src/backend/utils/misc/rls.c
 *
 *-------------------------------------------------------------------------
*/
#include "postgres.h"

#include "access/htup.h"
#include "access/htup_details.h"
#include "access/transam.h"
#include "catalog/namespace.h"
#include "catalog/pg_class.h"
#include "miscadmin.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/rls.h"
#include "utils/syscache.h"
#include "utils/varlena.h"


/*
 * check_enable_rls
 *
 * 根据关系、行安全设置和当前角色，确定RLS是否适用于该查询。 RLS_NONE_ENV表示，虽然
 * RLS不适用于此查询，但环境的变化可能会改变这一点。 RLS_NONE意味着关系上根本没有RLS，因此
 * 我们不需要担心它。 RLS_ENABLED表示应为该表实施RLS，如果环境发生变化，计划缓存需要
 * 失效。
 *
 * 通过checkAsUser以另一种角色处理检查（对于视图等）。传递InvalidOid以检查当前用户。
 *
 * 如果noError被设置为'true'，那么如果用户尝试绕过RLS且不被允许，我们只需返回
 * RLS_ENABLED，而不是执行ereport()。 这允许用户检查RLS是否启用，而无需
 * 处理实际的错误情况（例如：尝试决定用户是否应从关系中获取数据作为错误的一部分的错误情况）。
 */
int check_enable_rls(Oid fc_relid, Oid fc_checkAsUser, bool fc_noError)
{
	Oid			fc_user_id = fc_checkAsUser ? fc_checkAsUser : GetUserId();
	HeapTuple	fc_tuple;
	Form_pg_class fc_classform;
	bool		fc_relrowsecurity;
	bool		fc_relforcerowsecurity;
	bool		fc_amowner;

	/* 内置关系无事可做 */
	if (fc_relid < (Oid) FirstNormalObjectId)
		return RLS_NONE;

	/* 获取关系的relrowsecurity和relforcerowsecurity标志 */
	fc_tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(fc_relid));
	if (!HeapTupleIsValid(fc_tuple))
		return RLS_NONE;
	fc_classform = (Form_pg_class) GETSTRUCT(fc_tuple);

	fc_relrowsecurity = fc_classform->relrowsecurity;
	fc_relforcerowsecurity = fc_classform->relforcerowsecurity;

	ReleaseSysCache(fc_tuple);

	/* 如果关系没有RLS，则无事可做 */
	if (!fc_relrowsecurity)
		return RLS_NONE;

	/*
	 * BYPASSRLS用户始终绕过RLS。 请注意，超级用户始终
	 * 被认为具有BYPASSRLS。
	 *
	 * 返回RLS_NONE_ENV以指示此决定取决于
	 * 环境（在这种情况下，user_id）。
	 */
	if (has_bypassrls_privilege(fc_user_id))
		return RLS_NONE_ENV;

	/*
	 * 表的所有者通常绕过RLS，除非该表已由
	 * 所有者设置为FORCE ROW SECURITY，并且这不是引用
	 * 完整性检查。
	 *
	 * 返回RLS_NONE_ENV以指示此决定取决于
	 * 环境（在这种情况下，user_id）。
	 */
	fc_amowner = pg_class_ownercheck(fc_relid, fc_user_id);
	if (fc_amowner)
	{
		/*
		 * 如果在关系上设置了FORCE ROW LEVEL SECURITY，则我们
		 * 应返回RLS_ENABLED以指示应应用RLS。
		 * 如果没有，或者如果我们处于InNoForceRLSOperation上下文中，则返回
		 * RLS_NONE_ENV。
		 *
		 * InNoForceRLSOperation表示即使表已设置FORCE RLS，我们也不应应用RLS - 如果当前用户是所有者。
		 * 这特别是为了确保引用完整性检查
		 * 仍然能够正确运行。
		 *
		 * 这故意仅在我们检查完用户是否
		 * 是表的所有者后执行，这对引用
		 * 完整性检查来说应该始终是这样的。
		 */
		if (!fc_relforcerowsecurity || InNoForceRLSOperation())
			return RLS_NONE_ENV;
	}

	/*
	 * 我们应该应用RLS。 然而，用户可能会关闭row_security
	 * GUC以获得强制错误。
	 */
	if (!row_security && !fc_noError)
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("query would be affected by row-level security policy for table \"%s\"",
						get_rel_name(fc_relid)),
				 fc_amowner ? errhint("To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY.") : 0));

	/* RLS应为此关系完全启用。 */
	return RLS_ENABLED;
}

/*
 * row_security_active
 *
 * check_enable_rls被包装为SQL可调用函数，除了
 * RLS_NONE_ENV和RLS_NONE在这个目的上是相同的。
 */
Datum row_security_active(PG_FUNCTION_ARGS)
{
	/* 按OID */
	Oid			fc_tableoid = PG_GETARG_OID(0);
	int			fc_rls_status;

	fc_rls_status = check_enable_rls(fc_tableoid, InvalidOid, true);
	PG_RETURN_BOOL(fc_rls_status == RLS_ENABLED);
}

Datum row_security_active_name(PG_FUNCTION_ARGS)
{
	/* 按限定名称 */
	text	   *fc_tablename = PG_GETARG_TEXT_PP(0);
	RangeVar   *fc_tablerel;
	Oid			fc_tableoid;
	int			fc_rls_status;

	/* 查找表名。无法锁定-我们可能没有权限。 */
	fc_tablerel = makeRangeVarFromNameList(textToQualifiedNameList(fc_tablename));
	fc_tableoid = RangeVarGetRelid(fc_tablerel, NoLock, false);

	fc_rls_status = check_enable_rls(fc_tableoid, InvalidOid, true);
	PG_RETURN_BOOL(fc_rls_status == RLS_ENABLED);
}
